package com.dongrj.framework.csv;

import com.dongrj.framework.core.utils.CollectionUtils;
import com.dongrj.framework.core.utils.DateUtils;
import com.dongrj.framework.core.utils.StringUtils;
import com.google.common.collect.Lists;
import com.opencsv.CSVReader;
import com.opencsv.CSVReaderBuilder;
import com.opencsv.CSVWriter;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author dongrongjun
 * @date 2020/4/2
 */
@Slf4j
public class CsvUtils {

    private static final Logger logger = LoggerFactory.getLogger(CsvUtils.class);

    public enum Format {
        FORMAT_LONG, FORMAT_DATE
    }

    public static <T> void exportCsv(String csvName, List<String> headerList, List<T> list, Function<T, List<String>> function, HttpServletResponse response) {
        exportCsv(false, csvName, headerList, list, function, response);
    }


    /**
     * @param isAlert 前端是否需要提醒
     */
    public static <T> void exportCsv(boolean isAlert, String csvName, List<String> headerList, List<T> list, Function<T, List<String>> function, HttpServletResponse response) {
        checkDataIsOk(list, isAlert);
        setCsvNameAndHeader(csvName, response);
        CSVWriter writer = null;
        try {
            writer = new CSVWriter(response.getWriter());
            fillCsv(writer, headerList, list, function);
        } catch (Exception e) {
            logger.error("导出Csv失败!", e);
        } finally {
            closeWrite(writer);
        }
    }

    /**
     * TODO 暂定文件上传
     *
     * @param file      文件
     * @param arrayToT  数组转实体
     * @param tToR      消费实体
     * @param skipLines 跳过行数
     * @param charset   字符集
     */
    public static <T, R> List<R> importCsv(MultipartFile file, Function<String[], T> arrayToT, Function<T, R> tToR, int skipLines, String charset) throws IOException {
        return importCsv(file.getInputStream(), arrayToT, tToR, skipLines, charset);
    }

    /**
     * TODO 暂定文件上传
     *
     * @param in        文件流
     * @param arrayToT  数组转实体
     * @param tToR      消费实体
     * @param skipLines 跳过行数
     * @param charset   字符集
     */
    public static <T, R> List<R> importCsv(InputStream in, Function<String[], T> arrayToT, Function<T, R> tToR, int skipLines, String charset) throws IOException {
        Reader reader = new InputStreamReader(in, charset);
        CSVReaderBuilder cSVReaderBuilder = new CSVReaderBuilder(reader).withSkipLines(skipLines);
        CSVReader csvReader = cSVReaderBuilder.build();
        String[] nextLine;
        long recordsRead = csvReader.getRecordsRead();
        List<R> list = new ArrayList<>((int) recordsRead);
        try {
            while ((nextLine = csvReader.readNext()) != null) {
                T applyT = arrayToT.apply(nextLine);
                R applyR = tToR.apply(applyT);
                list.add(applyR);
            }
        } catch (IOException e) {
            e.printStackTrace();
            log.error("importCsvRow:", e);
            throw new CsvImportException("Csv导入失败!");
        } finally {
            closeReader(csvReader);
        }
        return list;
    }

    /**
     * 一次性读取后处理
     *
     * @param csvFilePath Csv文件路径
     * @param skipLines   跳过的行数
     * @param function1   String[] —> T 函数
     * @param function2   T处理函数
     */
    public static <T, R> List<R> importCsvRows(String csvFilePath, int skipLines, Function<String[], T> function1, Function<T, R> function2) {
        List<String[]> csvArray = getArray(csvFilePath, skipLines, StandardCharsets.UTF_8);
        List<R> list = new ArrayList<>(CollectionUtils.size(csvArray));
        for (String[] strings : csvArray) {
            T applyT = function1.apply(strings);
            R applyR = function2.apply(applyT);
            list.add(applyR);
        }
        return list;
    }

    /**
     * 获取Csv文件数组内容
     *
     * @param csvFilePath csv文件路径
     * @param skipLines   跳过行数
     */
    private static List<String[]> getArray(String csvFilePath, int skipLines, Charset charset) {
        CSVReader csvReader = getCSVReader(csvFilePath, skipLines, charset);
        List<String[]> csvArray = getAllCsvArray(csvReader);
        closeReader(csvReader);
        return csvArray;
    }

    /**
     * 一次性读取后处理
     *
     * @param csvFilePath Csv文件路径
     * @param skipLines   跳过的行数
     * @param function    String[] —> T 函数
     */
    public static <T> List<T> importCsvRows(String csvFilePath, int skipLines, Function<String[], T> function) {
        return importCsvRows(StandardCharsets.UTF_8, csvFilePath, skipLines, function);
    }

    /**
     * 一次性读取后处理
     *
     * @param csvFilePath Csv文件路径
     * @param skipLines   跳过的行数
     * @param function    String[] —> T 函数
     */
    public static <T> List<T> importCsvRows(Charset charset, String csvFilePath, int skipLines, Function<String[], T> function) {
        List<String[]> csvArray = getArray(csvFilePath, skipLines, charset);
        List<T> list = new ArrayList<>(CollectionUtils.size(csvArray));
        for (String[] strings : csvArray) {
            T applyT = function.apply(strings);
            list.add(applyT);
        }
        return list;
    }


    private static List<String[]> getAllCsvArray(CSVReader csvReader) {
        List<String[]> result = Lists.newArrayList();
        try {
            result = csvReader.readAll();
        } catch (IOException e) {
            e.printStackTrace();
            log.error("importCsvRows.readAll.e={}", e);
        }
        return result;
    }

    /**
     * 一次性读取后处理
     *
     * @param csvFilePath Csv文件路径
     * @param skipLines   跳过的行数
     * @param function    String[] —> T 函数
     * @param consumer    T处理函数
     */
    public static <T> void importCsvRows(String csvFilePath, int skipLines, Function<String[], T> function, Consumer<T> consumer) {
        CSVReader csvReader = getCSVReader(csvFilePath, skipLines, StandardCharsets.UTF_8);
        List<String[]> csvArray = getAllCsvArray(csvReader);
        closeReader(csvReader);
        for (String[] strings : csvArray) {
            T applyT = function.apply(strings);
            consumer.accept(applyT);
        }
    }

    /**
     * 逐行处理
     *
     * @param csvFilePath Csv文件路径
     * @param skipLines   跳过的行数
     * @param function1   String[] —> T 函数
     * @param function2   T处理函数
     */
    public static <T, R> List<R> importCsvRow(String csvFilePath, int skipLines, Function<String[], T> function1, Function<T, R> function2) {
        CSVReader csvReader = getCSVReader(csvFilePath, skipLines, StandardCharsets.UTF_8);
        String[] nextLine;
        Long recordsRead = csvReader.getRecordsRead();
        List<R> list = new ArrayList<>(recordsRead.intValue());
        try {
            while ((nextLine = csvReader.readNext()) != null) {
                T applyT = function1.apply(nextLine);
                R applyR = function2.apply(applyT);
                list.add(applyR);
            }
        } catch (IOException e) {
            e.printStackTrace();
            log.error("importCsvRow:", e);
            throw new CsvImportException("Csv导入失败:" + csvFilePath);
        } finally {
            closeReader(csvReader);
        }
        return list;
    }

    /**
     * 逐行处理
     *
     * @param csvFilePath Csv文件路径
     * @param skipLines   跳过的行数
     * @param function1   String[] —> T 函数
     * @param function2   T处理函数
     */
    public static <T, R> List<R> importCsvRowPart(String csvFilePath, int skipLines, Function<String[], T> function1, Function<T, R> function2) {

        return null;
    }


    /**
     * 逐行处理
     *
     * @param csvFilePath Csv文件路径
     * @param skipLines   跳过的行数
     * @param function    String[] —> T 函数
     * @param consumer    T处理函数
     */
    public static <T> CsvImportResult<T> importCsvRow(String csvFilePath, int skipLines, Function<String[], T> function, Consumer<T> consumer) {
        CsvImportResult<T> csvImportResult = new CsvImportResult<>();
        CSVReader csvReader = getCSVReader(csvFilePath, skipLines, StandardCharsets.UTF_8);
        long rows = 0;
        long time = System.currentTimeMillis();
        String[] nextLine;
        try {
            while ((nextLine = csvReader.readNext()) != null) {
                T applyT = function.apply(nextLine);
                rows++;
                if (Objects.nonNull(consumer)) {
                    consumer.accept(applyT);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            log.error("importCsvRow:", e);
            throw new CsvImportException("Csv导入失败:" + csvFilePath);
        } finally {
            closeReader(csvReader);
        }
        time = System.currentTimeMillis() - time;
        csvImportResult.setRows(rows);
        csvImportResult.setTime(time);
        return csvImportResult;
    }


    /**
     * 逐行处理
     *
     * @param csvFilePath Csv文件路径
     * @param logSize     记录记日志的大小
     * @param skipLines   跳过的行数
     * @param function    String[] —> T 函数
     * @param consumer    T处理函数
     */
    public static <T> CsvImportResult<T> importCsvRow(String csvFilePath, Long logSize, int skipLines, Function<String[], T> function, Consumer<T> consumer) {
        if (logSize == null || logSize < 0) {
            return importCsvRow(csvFilePath, skipLines, function, consumer);
        }
        CSVReader csvReader = getCSVReader(csvFilePath, skipLines, StandardCharsets.UTF_8);
        long rows = 0;
        long time = System.currentTimeMillis();
        CsvImportResult<T> csvImportResult = new CsvImportResult<>();
        String[] nextLine;
        try {
            while ((nextLine = csvReader.readNext()) != null) {
                T applyT = function.apply(nextLine);
                consumer.accept(applyT);
                rows++;
                if (rows % logSize == 0) {
                    log.info("importCsvRow.csvFilePath={},rows={},time={}", csvFilePath, rows, (System.currentTimeMillis() - time));
                }
            }
            log.info("importCsvRow.csvFilePath={},rows={},time={}", csvFilePath, rows, (System.currentTimeMillis() - time));
        } catch (IOException e) {
            e.printStackTrace();
            log.error("importCsvRow:", e);
            throw new CsvImportException("Csv导入失败:" + csvFilePath + "_" + rows + "_" + (System.currentTimeMillis() - time));
        } finally {
            closeReader(csvReader);
        }
        time = System.currentTimeMillis() - time;
        csvImportResult.setRows(rows);
        csvImportResult.setTime(time);
        return csvImportResult;
    }

    /**
     * 逐行处理
     *
     * @param csvFilePath Csv文件路径
     * @param skipLines   跳过的行数
     * @param function    String[] —> T 函数
     * @param consumers   T处理函数
     */
    public static <T> void importCsvRowBatch(String csvFilePath, int skipLines, Function<String[], T> function, Integer batchSize, Consumer<List<T>> consumers) {
        CSVReader csvReader = getCSVReader(csvFilePath, skipLines, StandardCharsets.UTF_8);
        String[] nextLine;
        List<T> batchList = new ArrayList<>(batchSize);
        try {
            while ((nextLine = csvReader.readNext()) != null) {
                T applyT = function.apply(nextLine);
                if (Objects.nonNull(applyT)) {
                    batchList.add(applyT);
                    if (CollectionUtils.sizeEquals(batchList, batchSize)) {
                        long start = System.currentTimeMillis();
                        consumers.accept(batchList);
                        long end = System.currentTimeMillis();
                        log.info("importCsvRowPart.part.time={}", (end - start));
                        batchList.clear();
                    }
                }
            }
            if (CollectionUtils.isNotEmpty(batchList)) {
                consumers.accept(batchList);
            }
        } catch (IOException e) {
            e.printStackTrace();
            log.error("importCsvRow:", e);
            throw new CsvImportException("Csv导入失败:" + csvFilePath);
        } finally {
            closeReader(csvReader);
        }
    }

    /**
     * 获取CSVReader
     *
     * @param csvFilePath Csv文件路径
     * @param skipLines   跳过的行数
     * @return
     */
    private static CSVReader getCSVReader(String csvFilePath, int skipLines, Charset charset) {
        Reader reader = null;
        try {
            reader = Files.newBufferedReader(Paths.get(csvFilePath), charset);
        } catch (IOException e) {
            e.printStackTrace();
            log.error("importCsv.reader:", e);
            throw new RuntimeException("Csv文件解析错误!");
        }
        CSVReaderBuilder cSVReaderBuilder = new CSVReaderBuilder(reader).withSkipLines(skipLines);
        return cSVReaderBuilder.build();
    }


    private static <T> void checkDataIsOk(List<T> list, boolean isCheck) {
        if (!isCheck) {
            return;
        }
        if (CollectionUtils.isEmpty(list)) {
            throw new RuntimeException("没有数据无法导出!");
        }
    }

    public static <T> List<T> importCsv(MultipartFile multipartFile, Function<String[], T> function) throws IOException {
        CSVReader csvReader = getCSVReader(multipartFile);
        String[] headers = csvReader.readNext();
        if (CollectionUtils.isEmpty(headers)) {
            closeReader(csvReader);
            return Lists.newArrayList();
        }
        List<T> result = getResult(csvReader, function);
        closeReader(csvReader);
        return result;
    }

    private static CSVReader getCSVReader(MultipartFile multipartFile) throws IOException {
        InputStream inputStream = multipartFile.getInputStream();
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        return new CSVReader(inputStreamReader);
    }

    private static <T> List<T> getResult(CSVReader csvReader, Function<String[], T> function) throws IOException {
        List<String[]> rows = csvReader.readAll();
        List<T> result = Lists.newArrayList();
        for (String[] row : rows) {
            T apply = function.apply(row);
            result.add(apply);
        }
        return result;
    }

    public static <T> CsvImportResult<T> importCsvWithHeader(MultipartFile multipartFile, Function<String[], T> function) throws IOException {
        CSVReader csvReader = getCSVReader(multipartFile);
        String[] headers = csvReader.readNext();
        if (CollectionUtils.isEmpty(headers)) {
            final CsvImportResult<T> tCsvImportResult = new CsvImportResult<>();
            tCsvImportResult.setHeaders(headers);
            return tCsvImportResult;
        }
        List<T> result = getResult(csvReader, function);
        closeReader(csvReader);
        CsvImportResult<T> csvImportResult = new CsvImportResult<>();
        csvImportResult.setData(result);
        csvImportResult.setHeaders(headers);
        return csvImportResult;
    }


    private static void setCsvNameAndHeader(String csvName, HttpServletResponse response) {
        if (Objects.isNull(csvName)) {
            csvName = "";
        }
        csvName = csvName + new SimpleDateFormat("_yyyy-MM-dd hhmmss").format(new Date());
        try {
            response.reset();
            csvName = new String(csvName.getBytes("gbk"), StandardCharsets.ISO_8859_1);
            response.setHeader("Content-Type", "application/force-download");
            response.setHeader("Content-Type", "application/vnd.ms-excel");
            response.setHeader("Content-disposition", "attachment;  filename=" + csvName + ".csv");
            response.setCharacterEncoding("gbk");
        } catch (UnsupportedEncodingException uee) {
            throw new RuntimeException("请求头参数设置异常!");
        }
    }

    private static <T> void fillCsv(CSVWriter writer, List<String> headerList, List<T> list, Function<T, List<String>> function) {
        writeHeader(writer, toArray(headerList));
        writeBody(writer, list, function);
    }

    private static void writeHeader(CSVWriter writer, String[] cnFields) {
        writer.writeNext(cnFields);
    }

    private static <T> void writeBody(CSVWriter writer, List<T> list, Function<T, List<String>> function) {
        for (T item : list) {
            if (Objects.isNull(item)) {
                continue;
            }
            writer.writeNext(toArray(function.apply(item)));
        }
        closeWrite(writer);
    }

    private static void closeWrite(CSVWriter writer) {
        if (Objects.nonNull(writer)) {
            try {
                writer.flush();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void closeReader(CSVReader csvReader) {
        if (Objects.nonNull(csvReader)) {
            try {
                csvReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static String format(Object obj, Format format) {
        if (format == Format.FORMAT_LONG) {
            String value = obj.toString();
            if (StringUtils.length(value) > 11) {
                return String.format("=\"%s\"", obj.toString());
            }
            return value;
        }
        if (format == Format.FORMAT_DATE) {
            if (obj instanceof Date) {
                return DateUtils.toString((Date) obj);
            }
        }
        return obj.toString();
    }

    public static String translate(Object value, Map<String, String> translateMap) {
        if (Objects.isNull(value)) {
            return StringUtils.EMPTY;
        }
        String str = value.toString();
        String translateValue = translateMap.get(str);
        if (StringUtils.isEmpty(translateValue)) {
            return str;
        }
        return translateValue;
    }

    private static String[] toArray(Collection<String> collections) {
        if (Objects.isNull(collections)) {
            collections = Lists.newArrayList();
        }
        String[] strings = new String[collections.size()];
        return collections.toArray(strings);
    }
}


